package com.hsu.yupao.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hsu.yupao.common.ErrorCode;
import com.hsu.yupao.exception.BusinessException;
import com.hsu.yupao.model.domain.Chat;
import com.hsu.yupao.model.domain.Team;
import com.hsu.yupao.model.domain.User;
import com.hsu.yupao.model.request.ChatRequest;
import com.hsu.yupao.model.vo.MessageVO;
import com.hsu.yupao.model.vo.WebSocketVO;
import com.hsu.yupao.service.ChatService;
import com.hsu.yupao.mapper.ChatMapper;
import com.hsu.yupao.service.TeamService;
import com.hsu.yupao.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.hsu.yupao.constant.ChatConstant.*;
import static com.hsu.yupao.constant.UserConstant.ADMIN_ROLE;

/**
* @author 幽琴健
* @description 针对表【chat(聊天信息表)】的数据库操作Service实现
* @createDate 2024-06-03 16:48:19
*/
@Service
public class ChatServiceImpl extends ServiceImpl<ChatMapper, Chat>
    implements ChatService{

    @Resource
    private RedisTemplate<String, List<MessageVO>> redisTemplate;

    @Resource
    private UserService userService;

    @Resource
    private TeamService teamService;

    /**
     * 获取指定聊天类型下的私聊聊天记录。
     * @param chatRequest 包含聊天目标用户ID的请求对象。
     * @param chatType 聊天类型（例如：私聊）。
     * @param loginUser 当前登录的用户对象。
     * @return 返回与指定用户的私聊记录列表。
     * @throws BusinessException 如果输入参数无效或查询过程中发生错误。
     */
    @Override
    public List<MessageVO> getPrivateChat(ChatRequest chatRequest, int chatType, User loginUser) {
        Long toId = chatRequest.getToId();
        if (toId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "私聊接收用户ID不能为空");
        }
        // 尝试从缓存中获取聊天记录
        String id = loginUser.getId() + "" + toId;
        List<MessageVO> chatRecords = getCache(CACHE_CHAT_PRIVATE, id);
        if (chatRecords != null) {
            return chatRecords;  // 如果缓存中有记录，直接返回这些记录
        }
        // 缓存中没有找到记录，从数据库查询
        LambdaQueryWrapper<Chat> chatLambdaQueryWrapper = new LambdaQueryWrapper<>();
        chatLambdaQueryWrapper
                .and(wrapper -> wrapper
                        .eq(Chat::getFromId, loginUser.getId()).eq(Chat::getToId, toId)// 查找当前登录用户作为发送者和指定用户作为接收者的记录
                        .or()
                        .eq(Chat::getToId, loginUser.getId()).eq(Chat::getFromId, toId))// 当前登录用户作为接收者和指定用户作为发送者的记录
                .eq(Chat::getChatType, chatType);  // 确保只查找对应聊天类型的记录
        List<Chat> list = this.list(chatLambdaQueryWrapper);
        // 将查询到的聊天数据转换为视图对象列表
        List<MessageVO> messageVoList = list.stream().map(chat -> {
            // 判断消息是否由当前登录用户发送
            boolean isMyMessage = chat.getFromId().equals(loginUser.getId());
            // 为每条聊天记录创建视图对象
            MessageVO messageVO = chatResult(
                    isMyMessage ? loginUser.getId() : chat.getFromId(),  // 正确设置发送者ID
                    isMyMessage ? toId : chat.getToId(),  // 正确设置接收者ID
                    chat.getText(),
                    chatType,
                    chat.getCreateTime()
            );
            // 标记消息是否为“我的消息”
            messageVO.setIsMy(isMyMessage);
            return messageVO;
        }).collect(Collectors.toList());
        // 将新查询的结果保存到缓存
        saveCache(CACHE_CHAT_PRIVATE, id, messageVoList);
        return messageVoList;  // 返回最终聊天记录列表
    }

    /**
     * 获取指定团队的聊天记录。
     * @param chatRequest 包含队伍聊天室ID的请求对象。
     * @param chatType 聊天类型，用于区分不同的聊天环境（例如团队聊天）。
     * @param loginUser 当前登录的用户，用于识别哪些消息是由当前用户发送。
     * @return 返回构建好的消息视图对象列表，这些对象包含了聊天信息及其相关属性。
     * @throws BusinessException 如果队伍ID为空或其他业务规则不满足，则抛出业务异常。
     */
    @Override
    public List<MessageVO> getTeamChat(ChatRequest chatRequest, int chatType, User loginUser) {
        Long teamId = chatRequest.getTeamId();
        if (teamId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍ID不能为空");
        }
        // 尝试从缓存中获取团队聊天记录
        String id = String.valueOf(teamId);
        List<MessageVO> chatRecords = getCache(CACHE_CHAT_TEAM, id);
        if (chatRecords != null) {
            // 检查消息是否为当前用户发送，更新消息视图的归属状态
            List<MessageVO> messageVOs = checkIsMyMessage(loginUser, chatRecords);
            // 更新缓存以反映最新的消息归属状态
            saveCache(CACHE_CHAT_TEAM, id, messageVOs);
            return messageVOs;  // 如果缓存中有记录，直接返回这些记录
        }
        // 缓存中没有找到记录，查询数据库获取团队聊天记录
        Team team = teamService.getById(teamId);
        LambdaQueryWrapper<Chat> chatLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查找指定团队ID和聊天类型的记录
        chatLambdaQueryWrapper.eq(Chat::getChatType, chatType).eq(Chat::getTeamId, teamId);
        // 构建消息视图列表
        List<MessageVO> messageVOs = returnMessage(loginUser, team.getUserId(), chatLambdaQueryWrapper);
        // 将查询结果保存到缓存中
        saveCache(CACHE_CHAT_TEAM, id, messageVOs);
        return messageVOs;  // 返回团队聊天记录列表
    }

    /**
     * 获取大厅聊天记录。
     * 首先尝试从缓存中获取记录，如果没有找到，将从数据库中查询。
     * @param chatType 聊天类型，用于指定查询的聊天类型。
     * @param loginUser 当前登录的用户，用于确定消息归属。
     * @return 返回大厅的聊天记录列表。
     */
    @Override
    public List<MessageVO> getHallChat(int chatType, User loginUser) {
        // 尝试从缓存中获取大厅聊天记录，使用登录用户的ID作为键
        List<MessageVO> chatRecords = getCache(CACHE_CHAT_HALL, String.valueOf(loginUser.getId()));
        if (chatRecords != null) {
            // 检查消息是否由当前登录用户发送，并更新消息视图的归属状态
            List<MessageVO> messageVOs = checkIsMyMessage(loginUser, chatRecords);
            // 更新缓存，以反映最新的消息归属状态
            saveCache(CACHE_CHAT_HALL, String.valueOf(loginUser.getId()), messageVOs);
            return messageVOs;  // 如果缓存中有记录，直接返回这些记录
        }
        // 缓存中没有找到记录，查询数据库获取大厅聊天记录
        LambdaQueryWrapper<Chat> chatLambdaQueryWrapper = new LambdaQueryWrapper<>();
        chatLambdaQueryWrapper.eq(Chat::getChatType, chatType);  // 确保只查找大厅聊天类型的记录
        List<MessageVO> messageVOs = returnMessage(loginUser, null, chatLambdaQueryWrapper);  // 构建消息视图列表
        saveCache(CACHE_CHAT_HALL, String.valueOf(loginUser.getId()), messageVOs);  // 将查询结果保存到缓存中
        return messageVOs;  // 返回大厅聊天记录列表
    }

    /**
     * 保存聊天记录到缓存。
     * @param redisKey 缓存键，区分不同类型的聊天缓存。
     * @param id 用户或会话标识符，用于构造具体的缓存键。
     * @param messageVOs 要保存的消息对象列表。
     */
    @Override
    public void saveCache(String redisKey, String id, List<MessageVO> messageVOs) {
        try {
            ValueOperations<String, List<MessageVO>> valueOperations = redisTemplate.opsForValue();
            // 随机增加2到3之间的小数，用于避免所有缓存同时过期造成的缓存雪崩
            int randomAdditionalTime = RandomUtil.randomInt(2, 3);
            if (redisKey.equals(CACHE_CHAT_HALL)) {
                // 大厅聊天使用单独的缓存键
                valueOperations.set(redisKey, messageVOs, 2 + randomAdditionalTime / 10, TimeUnit.MINUTES);
            } else {
                // 其他类型的聊天（如私聊或队伍聊天）使用用户ID或会话ID来构造缓存键
                valueOperations.set(redisKey + id, messageVOs, 2 + randomAdditionalTime / 10, TimeUnit.MINUTES);
            }
        } catch (Exception e) {
            log.error("Redis set key error", e);
        }
    }

    /**
     * 从缓存中获取聊天记录。
     * @param redisKey 缓存键前缀，用于区分聊天类型。
     * @param id 用户或会话标识符，用于构造具体的缓存键。
     * @return 返回存储在缓存中的聊天记录列表，如果没有找到则返回null。
     */
    @Override
    public List<MessageVO> getCache(String redisKey, String id) {
        ValueOperations<String, List<MessageVO>> valueOperations = redisTemplate.opsForValue();
        List<MessageVO> chatRecords;
        if (redisKey.equals(CACHE_CHAT_HALL)) {
            // 大厅聊天使用的是单一的全局键
            chatRecords = valueOperations.get(redisKey);
        } else {
            // 其他聊天（如私聊或队伍聊天）构造缓存键时包含用户ID或会话ID
            chatRecords = valueOperations.get(redisKey + id);
        }
        return chatRecords;
    }

    /**
     * 创建一个聊天消息视图对象，包含发送者和接收者的详细信息。
     * @param userId 发送者用户ID。
     * @param toId 接收者用户ID。
     * @param text 聊天消息内容。
     * @param chatType 聊天类型（如私聊、群聊等）。
     * @param createTime 消息的创建时间。
     * @return 返回构建的 MessageVO 对象，包含完整的聊天消息详情。
     */
    @Override
    public MessageVO chatResult(Long userId, Long toId, String text, Integer chatType, Date createTime) {
        MessageVO messageVO = new MessageVO();
        User fromUser = userService.getById(userId);
        User toUser = userService.getById(toId);
        WebSocketVO fromWebSocketVO = new WebSocketVO();
        WebSocketVO toWebSocketVO = new WebSocketVO();
        BeanUtils.copyProperties(fromUser, fromWebSocketVO);  // 复制发送者的用户信息到 WebSocketVO
        BeanUtils.copyProperties(toUser, toWebSocketVO);      // 复制接收者的用户信息到 WebSocketVO
        messageVO.setFormUser(fromWebSocketVO);
        messageVO.setToUser(toWebSocketVO);
        messageVO.setChatType(chatType);
        messageVO.setText(text);
        messageVO.setCreateTime(DateUtil.format(createTime, "yyyy年MM月dd日 HH:mm:ss"));  // 格式化创建时间
        return messageVO;
    }

    /**
     * 删除指定的缓存键。
     * 用于清理无用或过期的缓存项。
     * @param key 要删除的缓存键。
     * @param id 与键相关联的额外标识符，用于精确删除。
     */
    @Override
    public void deleteKey(String key, String id) {
        // 根据提供的键删除缓存中的记录，如果是大厅聊天，则不需要额外的ID进行定位
        if (key.equals(CACHE_CHAT_HALL)) {
            redisTemplate.delete(key);
        } else {
            redisTemplate.delete(key + id);  // 如果不是大厅聊天，需要使用详细的键（包括ID）来删除
        }
    }

    /**
     * 检查并调整消息列表中的归属状态。
     * @param loginUser 当前登录的用户。
     * @param chatRecords 聊天记录列表。
     * @return 调整后的聊天记录列表。
     */
    private List<MessageVO> checkIsMyMessage(User loginUser, List<MessageVO> chatRecords) {
        return chatRecords.stream()
                .peek(chat -> {
                    // 如果消息的发送者ID不是当前登录用户的ID，并且消息标记为“我的”，则更正为非“我的”
                    if (chat.getFormUser().getId() != loginUser.getId() && chat.getIsMy()) {
                        chat.setIsMy(false);
                    }
                    // 如果消息的发送者ID是当前登录用户的ID，但消息未标记为“我的”，则更正为“我的”
                    if (chat.getFormUser().getId() == loginUser.getId() && !chat.getIsMy()) {
                        chat.setIsMy(true);
                    }
                })
                .collect(Collectors.toList());  // 收集调整后的结果并返回
    }

    /**
     * 将聊天记录转换为前端显示的消息视图对象列表。
     * @param loginUser 当前登录用户，用于确定哪些消息是用户发送的。
     * @param userId 团队或聊天组管理员的用户ID，用于高亮显示管理员发送的消息。
     * @param chatLambdaQueryWrapper 查询条件，用于数据库查询。
     * @return 转换后的消息视图对象列表。
     */
    private List<MessageVO> returnMessage(User loginUser, Long userId, LambdaQueryWrapper<Chat> chatLambdaQueryWrapper) {
        // 从数据库查询聊天记录
        List<Chat> chatList = this.list(chatLambdaQueryWrapper);
        return chatList.stream().map(chat -> {
            // 使用chatResult方法生成基础的消息视图对象
            MessageVO messageVO = chatResult(chat.getFromId(), chat.getText());
            // 检查消息发送者是否为团队的队长或具有管理员角色
            boolean isCaptain = userId != null && userId.equals(chat.getFromId());
            if (userService.getById(chat.getFromId()).getUserRole() == ADMIN_ROLE || isCaptain) {
                messageVO.setIsAdmin(true);  // 标记为管理员消息
            }
            // 检查是否为当前登录用户发送的消息
            if (chat.getFromId().equals(loginUser.getId())) {
                messageVO.setIsMy(true);  // 标记为“我的”消息
            }
            // 设置消息的创建时间
            messageVO.setCreateTime(DateUtil.format(chat.getCreateTime(), "yyyy年MM月dd日 HH:mm:ss"));
            return messageVO;
        }).collect(Collectors.toList());  // 收集并返回转换后的消息列表
    }

    /**
     * 创建一个聊天消息视图对象，仅包含发送者信息。
     * @param userId 发送者用户ID。
     * @param text 聊天消息内容。
     * @return 返回构建的 MessageVO 对象，只包含发送者信息和消息文本。
     */
    private MessageVO chatResult(Long userId, String text) {
        MessageVO messageVO = new MessageVO();
        User fromUser = userService.getById(userId);
        WebSocketVO fromWebSocketVO = new WebSocketVO();
        BeanUtils.copyProperties(fromUser, fromWebSocketVO);  // 复制发送者的用户信息到 WebSocketVO
        messageVO.setFormUser(fromWebSocketVO);
        messageVO.setText(text);
        return messageVO;
    }

}




